package com.gitee.easyopen.doc;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.ArrayUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import com.gitee.easyopen.annotation.Api;
import com.gitee.easyopen.annotation.ApiService;
import com.gitee.easyopen.doc.annotation.ApiDoc;
import com.gitee.easyopen.doc.annotation.ApiDocBean;
import com.gitee.easyopen.doc.annotation.ApiDocField;
import com.gitee.easyopen.doc.annotation.ApiDocMethod;

/**
 * 文档生成器
 * 
 * @author tanghc
 *
 */
public class ApiDocBuilder {
    
    private static String PACKAGE_PREFIX = "java.";

    private Map<String, List<ApiDocItem>> apiDocItemMap = new HashMap<String, List<ApiDocItem>>(64);

    public Collection<Entry<String, List<ApiDocItem>>> getDocItemEntry() {
        List<Entry<String, List<ApiDocItem>>> list = new ArrayList<>(this.apiDocItemMap.entrySet());
        
        for (Entry<String, List<ApiDocItem>> entry : list) {
            Collections.sort(entry.getValue());
        }
        
        Collections.sort(list, new Comparator<Entry<String, List<ApiDocItem>>>() {
            @Override
            public int compare(Entry<String, List<ApiDocItem>> o1, Entry<String, List<ApiDocItem>> o2) {
                return o1.getKey().compareTo(o2.getKey());
            }
        });
        
        return list;
    }

    public Map<String, List<ApiDocItem>> getApiDocItemMap() {
        return this.apiDocItemMap;
    }

    public void setApiDocItemMap(Map<String, List<ApiDocItem>> apiDocItemMap) {
        this.apiDocItemMap = apiDocItemMap;
    }

    public synchronized void addDocItem(ApiService apiService, Api api, Object handler, Method method) {
        ApiDocMethod apiDocMethod = AnnotationUtils.findAnnotation(method, ApiDocMethod.class);
        if(apiDocMethod == null) {
            return;
        }
        ApiDoc apiDoc = AnnotationUtils.findAnnotation(handler.getClass(), ApiDoc.class);
        String serviceName = apiDoc != null ? apiDoc.value() : handler.getClass().getSimpleName();
        if(StringUtils.isEmpty(serviceName)) {
            throw new RuntimeException("@ApiDoc注解value属性不能为空");
        }
        List<ApiDocItem> docItems = this.apiDocItemMap.get(serviceName);
        if (docItems == null) {
            docItems = new ArrayList<ApiDocItem>();
            this.apiDocItemMap.put(serviceName, docItems);
        }
        docItems.add(this.buildDocItem(api,apiDocMethod, method));
    }

    private ApiDocItem buildDocItem(Api api,ApiDocMethod apiDocMethod, Method method) {
        ApiDocItem docItem = new ApiDocItem();

        String name = api.name();
        String description = apiDocMethod.description();
        String remark = apiDocMethod.remark();

        docItem.setName(name);
        docItem.setVersion(api.version());
        docItem.setDescription(description);
        docItem.setRemark(remark);
        
        List<ApiDocFieldDefinition> paramDefinitions = buildParamApiDocFieldDefinitions(apiDocMethod, method);
        List<ApiDocFieldDefinition> resultDefinitions = buildResultApiDocFieldDefinitions(apiDocMethod, method);
        
        docItem.setParamDefinitions(paramDefinitions);
        docItem.setResultDefinitions(resultDefinitions);

        return docItem;
    }
    
    private List<ApiDocFieldDefinition> buildParamApiDocFieldDefinitions(ApiDocMethod apiDocMethod,Method method) {
        List<ApiDocFieldDefinition> paramDefinitions = Collections.emptyList();
        
        ApiDocField[] params = apiDocMethod.params();
        Class<?> paramClass = apiDocMethod.paramClass();
        if(!ArrayUtils.isEmpty(params)) {
            paramDefinitions = buildApiDocFieldDefinitionsByApiDocFields(params);
        }else if(paramClass != Object.class) {
            paramDefinitions = this.buildApiDocFieldDefinitionsByClass(paramClass);
        }else {
            paramDefinitions = this.buildParamDefinitions(method);
        }
        
        return paramDefinitions;
    }
    
    private List<ApiDocFieldDefinition> buildResultApiDocFieldDefinitions(ApiDocMethod apiDocMethod,Method method) {
        List<ApiDocFieldDefinition> resultDefinitions = Collections.emptyList();
        
        Class<?> elClass = apiDocMethod.elementClass();
        if(elClass != Object.class) {
            return buildApiDocFieldDefinitionsByType(elClass);
        }
        ApiDocField[] results = apiDocMethod.results();
        Class<?> resultClass = apiDocMethod.resultClass();
        if(!ArrayUtils.isEmpty(results)) {
            resultDefinitions = buildApiDocFieldDefinitionsByApiDocFields(results);
        }else if(resultClass != Object.class) {
            resultDefinitions = this.buildApiDocFieldDefinitionsByClass(resultClass);
        }else {
            resultDefinitions = this.buildResultDefinitions(method);
        }

        return resultDefinitions;
    }
    
    private List<ApiDocFieldDefinition> buildApiDocFieldDefinitionsByClass(Class<?> paramClass) {
        ApiDocBean bean = AnnotationUtils.findAnnotation(paramClass, ApiDocBean.class);
        if(bean != null) {
            ApiDocField[] fields = bean.fields();
            if(!ArrayUtils.isEmpty(fields)) {
                return buildApiDocFieldDefinitionsByApiDocFields(fields);
            }
        }
        
        return buildApiDocFieldDefinitionsByType(paramClass);
    }
    
    private List<ApiDocFieldDefinition> buildApiDocFieldDefinitionsByApiDocFields(ApiDocField[] params) {
        ArrayList<ApiDocFieldDefinition> paramDefinitions = new ArrayList<ApiDocFieldDefinition>();
        for (ApiDocField apiDocField : params) {
            if(apiDocField.beanClass() != Object.class) {
                paramDefinitions.add(buildApiDocFieldDefinitionByClass(apiDocField, apiDocField.beanClass()));
            } else {
                paramDefinitions.add(buildApiDocFieldDefinition(apiDocField, null));
            }
        }
        return paramDefinitions;
    }

    private List<ApiDocFieldDefinition> buildParamDefinitions(Method method) {
        Class<?>[] types = method.getParameterTypes();
        if (types.length == 0) {
            return Collections.emptyList();
        }

        Class<?> paramClass = types[0];

        return buildApiDocFieldDefinitionsByType(paramClass);
    }

    private List<ApiDocFieldDefinition> buildResultDefinitions(Method method) {
        Class<?> type = method.getReturnType();
        if(type == Void.class) {
            return Collections.emptyList();
        }
        
        return buildApiDocFieldDefinitionsByType(type);
    }

    // 从api参数中构建
    private static List<ApiDocFieldDefinition> buildApiDocFieldDefinitionsByType(Class<?> clazz) {
        final List<String> fieldNameList = new ArrayList<String>();
        final List<ApiDocFieldDefinition> docDefinition = new ArrayList<ApiDocFieldDefinition>();

        // 找到类上面的ApiDocBean注解
        ApiDocBean apiDocBean = AnnotationUtils.findAnnotation(clazz, ApiDocBean.class);
        if(apiDocBean != null) {
            ApiDocField[] fields = apiDocBean.fields();
            for (ApiDocField apiDocField : fields) {
                docDefinition.add(buildApiDocFieldDefinition(apiDocField, null));
                fieldNameList.add(apiDocField.name());
            }
        }
        // 遍历参数对象中的属性
        ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                ApiDocField docField = AnnotationUtils.findAnnotation(field, ApiDocField.class);
                if (docField != null) { // 找到有注解的属性
                    ApiDocFieldDefinition fieldDefinition = buildApiDocFieldDefinition(docField, field);
                    Class<?> beanClass = docField.beanClass();
                    Class<?> targetClass = field.getType();
                    
                    if(beanClass != Object.class) {
                        fieldDefinition = buildApiDocFieldDefinitionByClass(docField, beanClass);
                    } else if(!isJavaType(targetClass)) { // 如果是自定义类
                        fieldDefinition = buildApiDocFieldDefinitionByClass(docField, targetClass);
                    }
                    
                    docDefinition.add(fieldDefinition);
                }
            }
        });

        return docDefinition;
    }
    
    private static boolean isJavaType(Class<?> type) {
        if(type.isPrimitive()) {
            return true;
        }
        return type.getPackage().getName().startsWith(PACKAGE_PREFIX);
    }
    
    private static ApiDocFieldDefinition buildApiDocFieldDefinitionByClass(ApiDocField docField, Class<?> clazz) {
        String name = docField.name();
        String type = DataType.OBJECT.getValue();
        String description = docField.description();
        boolean required = docField.required();
        String example = docField.example();
        
        ApiDocFieldDefinition fieldDefinition = new ApiDocFieldDefinition();
        fieldDefinition.setName(name);
        fieldDefinition.setDataType(type);
        fieldDefinition.setRequired(String.valueOf(required));
        fieldDefinition.setExample(example);
        fieldDefinition.setDescription(description);
        
        List<ApiDocFieldDefinition> elementsDefinition = buildApiDocFieldDefinitionsByType(clazz);
        fieldDefinition.setElements(elementsDefinition);
        
        return fieldDefinition;
    }
    
    private static ApiDocFieldDefinition buildApiDocFieldDefinition(ApiDocField docField, Field field) {
        String name = docField.name();
        String type = DataType.STRING.getValue();
        DataType dataType = docField.dataType();
        if(dataType == DataType.UNKNOW) {
            type = getFieldType(field);
        } else {
            type = dataType.getValue();
        }
        String description = docField.description();
        boolean required = docField.required();
        String example = docField.example();
        
        if(field != null) {
            name = field.getName();
        }
        
        ApiDocFieldDefinition fieldDefinition = new ApiDocFieldDefinition();
        fieldDefinition.setName(name);
        fieldDefinition.setDataType(type);
        fieldDefinition.setRequired(String.valueOf(required));
        fieldDefinition.setExample(example);
        fieldDefinition.setDescription(description);
        
        List<ApiDocFieldDefinition> elementsDefinition = buildElementListDefinition(docField);
        fieldDefinition.setElements(elementsDefinition);
        
        if(elementsDefinition.size() > 0) {
            fieldDefinition.setDataType(DataType.ARRAY.getValue());
        }
        
        return fieldDefinition;
    }
    
    private static String getFieldType(Field field) {
        if(field == null) {
            return DataType.STRING.getValue();
        }
        return field.getType().getSimpleName().toLowerCase();
    }
    
    private static List<ApiDocFieldDefinition> buildElementListDefinition(ApiDocField docField) {
        Class<?> elClass = docField.elementClass();
        if(elClass != Object.class) {
            return buildApiDocFieldDefinitionsByType(elClass);
        }else {
            return Collections.emptyList();
        }
    }


}
